/*
 * Copyright 2008 Zoundry LLC, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.zoundry.gaedt.core.models.application;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.zoundry.gaedt.core.ZGaeException;
import org.zoundry.gaedt.core.util.ZGaeEncrypter;
import org.zoundry.gaedt.core.util.ZGaeResourceUtil;
import org.zoundry.gaedt.core.util.ZGaeUTF8Util;
import org.zoundry.gaedt.core.util.ZGaeUtil;
import org.zoundry.gaedt.core.yaml.ZGaeYAMLModel;


/**
 * Models a single Google App Engine application. The model encapsulates several
 * different .yaml files, including app.yaml and .gae.yaml (the latter is a
 * tooling specific file that contains design time settings only, such as the
 * local port to use for testing).
 */
public class ZGaeApplicationModel
{
    private static final ZGaeEncrypter sPasswordEncrypter = new ZGaeEncrypter("Google App Engine | Application Model Password Encryption");

    /** The root location of the Google App Engine application. */
    private IContainer mApplicationRoot;
    /** The YAML found in app.yaml. */
    private ZGaeYAMLModel mAppYamlModel;
    /** The YAML found in gae.yaml. */
    private ZGaeYAMLModel mGaeYamlModel;

    /**
     * C'tor.
     *
     * @param aApplicationRoot
     */
    public ZGaeApplicationModel(IContainer aApplicationRoot)
    {
        setApplicationRoot(aApplicationRoot);
        load();
    }

    /**
     * Gets the application's name.
     */
    public String getName()
    {
        String name = (String) getAppYamlModel().query("application");
        if (name == null)
            name = "";
        return name;
    }

    /**
     * Sets the application name.
     *
     * @param aName
     */
    public void setName(String aName)
    {
        getAppYamlModel().put("application", aName);
    }

    /**
     * Gets the application's runtime type.
     */
    public String getRuntimeType()
    {
        String runtime = (String) getAppYamlModel().query("runtime");
        if (runtime == null)
            runtime = "";
        return runtime;
    }

    /**
     * Sets the application's runtime type.
     *
     * @param aType
     */
    public void setRuntimeType(String aType)
    {
        getAppYamlModel().put("runtime", aType);
    }

    /**
     * Gets the application's version number.
     */
    public int getVersion()
    {
        Number version = (Number) getAppYamlModel().query("version");
        if (version == null)
            version = 1;
        return version.intValue();
    }

    /**
     * Sets the application's version number.
     *
     * @param aVersion
     */
    public void setVersion(int aVersion)
    {
        getAppYamlModel().put("version", aVersion);
    }

    /**
     * Gets the application's api version number.
     */
    public int getApiVersion()
    {
        Number version = (Number) getAppYamlModel().query("api_version");
        if (version == null)
            version = 1;
        return version.intValue();
    }

    /**
     * Sets the application's api version number.
     *
     * @param aVersion
     */
    public void setApiVersion(int aVersion)
    {
        getAppYamlModel().put("api_version", aVersion);
    }

    /**
     * Gets the application's handlers.
     */
    public List<ZGaeHandlerModel> getHandlers()
    {
        return Collections.EMPTY_LIST;
    }

    /**
     * Adds a handler to the list of handlers.
     *
     * @param aHandler
     */
    public void addHandler(ZGaeHandlerModel aHandler)
    {
        List handlerList = (List) getAppYamlModel().query("handlers");
        if (handlerList == null)
            handlerList = new ArrayList();
        handlerList.add(aHandler.getYamlModel().getYamlData());
        getAppYamlModel().put("handlers", handlerList);
    }

    /**
     * Removes a handler at the given index.
     *
     * @param aHandlerIndex
     */
    public void removeHandler(int aHandlerIndex)
    {
        List handlerList = (List) getAppYamlModel().query("handlers");
        if (handlerList == null)
            return;
        if (aHandlerIndex >= handlerList.size())
            return;
        handlerList.remove(aHandlerIndex);
    }

    /**
     * Gets the application's dev-time port number.
     */
    public int getDevPort()
    {
        Number port = (Number) getGaeYamlModel().query("port");
        if (port == null)
            port = 8080;
        return port.intValue();
    }

    /**
     * Sets the dev-time port.
     *
     * @param aPort
     */
    public void setDevPort(int aPort)
    {
        getGaeYamlModel().put("port", aPort);
    }

    /**
     * Gets the application's dev-time address.
     */
    public String getDevAddress()
    {
        String address = (String) getGaeYamlModel().query("address");
        if (address == null)
            address = "";
        return address;
    }

    /**
     * Sets the application's dev-time address.
     *
     * @param aAddress
     */
    public void setDevAddress(String aAddress)
    {
        getGaeYamlModel().put("address", aAddress);
    }

    /**
     * Gets the application's dev-time SMTP host.
     */
    public String getDevSMTPHost()
    {
        String host = (String) getGaeYamlModel().query("smtp_host");
        if (host == null)
            host = "";
        return host;
    }

    /**
     * Sets the application's dev-time SMTP host.
     *
     * @param aAddress
     */
    public void setDevSMTPHost(String aSMTPHost)
    {
        getGaeYamlModel().put("smtp_host", aSMTPHost);
    }

    /**
     * Gets the application's dev-time SMTP port.
     */
    public int getDevSMTPPort()
    {
        Number port = (Number) getGaeYamlModel().query("smtp_port");
        if (port == null)
            port = 25;
        return port.intValue();
    }

    /**
     * Sets the dev-time SMTP port.
     *
     * @param aPort
     */
    public void setDevSMTPPort(int aPort)
    {
        getGaeYamlModel().put("smtp_port", aPort);
    }

    /**
     * Gets the application's dev-time SMTP username.
     */
    public String getDevSMTPUser()
    {
        String user = (String) getGaeYamlModel().query("smtp_user");
        if (user == null)
            user = "";
        return user;
    }

    /**
     * Sets the application's dev-time SMTP username.
     *
     * @param aAddress
     */
    public void setDevSMTPUser(String aUsername)
    {
        getGaeYamlModel().put("smtp_user", aUsername);
    }

    /**
     * Gets the application's dev-time SMTP password.
     */
    public String getDevSMTPPassword()
    {
        String encryptedPassword = (String) getGaeYamlModel().query("smtp_password");
        if (encryptedPassword == null)
            return "";
        String plaintextPassword = sPasswordEncrypter.decrypt(encryptedPassword);
        return plaintextPassword;
    }

    /**
     * Sets the application's dev-time SMTP username.
     *
     * @param aPlaintextPassword
     */
    public void setDevSMTPPassword(String aPlaintextPassword)
    {
        String encryptedPassword = null;
        if (aPlaintextPassword != null && !"".equals(aPlaintextPassword))
            encryptedPassword = sPasswordEncrypter.encrypt(aPlaintextPassword);
        getGaeYamlModel().put("smtp_password", encryptedPassword);
    }

    /**
     * Returns the application's 'debug imports' setting.
     */
    public boolean isDevDebugImports()
    {
        Boolean debugImports = (Boolean) getGaeYamlModel().query("debug_imports");
        if (debugImports == null)
            debugImports = Boolean.FALSE;
        return debugImports;
    }

    /**
     * Sets the application's 'debug imports' setting.
     *
     * @param aDebugImports
     */
    public void setDevDebugImports(boolean aDebugImports)
    {
        getGaeYamlModel().put("debug_imports", aDebugImports);
    }

    /**
     * Gets the application's 'debug' option.
     */
    public boolean isDevDebug()
    {
        Boolean debug = (Boolean) getGaeYamlModel().query("debug");
        if (debug == null)
            debug = Boolean.FALSE;
        return debug;
    }

    /**
     * Sets the application's 'debug' option.
     *
     * @param aDebug
     */
    public void setDevDebug(boolean aDebug)
    {
        getGaeYamlModel().put("debug", aDebug);
    }

    /**
     * Gets the application's 'require indexes' option.
     */
    public boolean isDevRequireIndexes()
    {
        Boolean requireIndexes = (Boolean) getGaeYamlModel().query("require_indexes");
        if (requireIndexes == null)
            requireIndexes = Boolean.FALSE;
        return requireIndexes;
    }

    /**
     * Sets the application's 'require indexes' option.
     *
     * @param aRequireIndexes
     */
    public void setDevRequireIndexes(boolean aRequireIndexes)
    {
        getGaeYamlModel().put("require_indexes", aRequireIndexes);
    }

    /**
     * Returns the application's 'enable sendmail' option.
     */
    public boolean isDevEnableSendmail()
    {
        Boolean enableSendmail = (Boolean) getGaeYamlModel().query("enable_sendmail");
        if (enableSendmail == null)
            enableSendmail = Boolean.FALSE;
        return enableSendmail;
    }

    /**
     * Sets the application's 'enable sendmail' dev-time option.
     *
     * @param aEnableSendmail
     */
    public void setDevEnableSendmail(boolean aEnableSendmail)
    {
        getGaeYamlModel().put("enable_sendmail", aEnableSendmail);
    }

    /**
     * Returns the application's 'show mail body' flag.
     */
    public boolean isDevShowMailBody()
    {
        Boolean showMailBody = (Boolean) getGaeYamlModel().query("show_mail_body");
        if (showMailBody == null)
            showMailBody = Boolean.FALSE;
        return showMailBody;
    }

    /**
     * Sets the application's 'show mail body' dev-time option.
     *
     * @param aShowMailBody
     */
    public void setDevShowMailBody(boolean aShowMailBody)
    {
        getGaeYamlModel().put("show_mail_body", aShowMailBody);
    }

    /**
     * Returns the application's 'auth domain' option.
     */
    public String getDevAuthDomain()
    {
        String authDomain = (String) getGaeYamlModel().query("auth_domain");
        if (authDomain == null)
            authDomain = "";
        return authDomain;
    }

    /**
     * Sets the application's dev-time auth domain.
     *
     * @param aAuthDomain
     */
    public void setDevAuthDomain(String aAuthDomain)
    {
        getGaeYamlModel().put("auth_domain", aAuthDomain);
    }

    /**
     * Returns the application's 'disable static caching' option.
     */
    public boolean isDevDisableStaticCaching()
    {
        Boolean disableStaticCaching = (Boolean) getGaeYamlModel().query("disable_static_caching");
        if (disableStaticCaching == null)
            disableStaticCaching = Boolean.FALSE;
        return disableStaticCaching;
    }

    /**
     * Sets the application's 'disable static caching' dev-time option.
     *
     * @param aShowMailBody
     */
    public void setDevDisableStaticCaching(boolean aShowMailBody)
    {
        getGaeYamlModel().put("disable_static_caching", aShowMailBody);
    }

    /**
     * Returns the app.yaml file location as an Eclipse {@link IFile}.
     */
    public IFile getAppYaml()
    {
        return (IFile) getChildResource("app.yml", "app.yaml");
    }

    /**
     * Returns the gae.yaml file location as an Eclipse {@link IFile}.
     */
    public IFile getGaeYaml()
    {
        return (IFile) getChildResource("gae.yml", "gae.yaml");
    }

    /**
     * Generic method to get a child resource of this application.  This method
     * will not return null - if none of the requested resource names correspond
     * to existing resources, the last one will be returned.
     *
     * @param aResourceName
     */
    protected IResource getChildResource(String ... aResourceName)
    {
        IFile file = null;
        for (String resourceName : aResourceName)
        {
            file = getApplicationRoot().getFile(new Path(resourceName));
            if (file.exists())
                return file;
        }
        return file;
    }

    /**
     * Loads the yaml information from any existing files.
     */
    protected void load()
    {
        IFile appYaml = getAppYaml();
        if (appYaml.exists())
        {
            String yamlData = getYamlData(appYaml);
            setAppYamlModel(new ZGaeYAMLModel(yamlData));
        }
        else
        {
            setAppYamlModel(new ZGaeYAMLModel());
        }
        IFile gaeYaml = getGaeYaml();
        if (gaeYaml.exists())
        {
            String yamlData = getYamlData(gaeYaml);
            setGaeYamlModel(new ZGaeYAMLModel(yamlData));
        }
        else
        {
            setGaeYamlModel(new ZGaeYAMLModel());
        }
    }

    /**
     * Saves the contents of the model to disk.
     *
     * @throws ZGaeException
     */
    public void save() throws CoreException, ZGaeException
    {
        writeYamlToFile(getAppYamlModel(), getAppYaml());
        writeYamlToFile(getGaeYamlModel(), getGaeYaml());
    }

    /**
     * Serialize and return just the app.yaml data.
     */
    public String serializeAppYaml()
    {
        return getAppYamlModel().serialize();
    }

    /**
     * Serialize and return just the gae.yaml data.
     */
    public String serializeGaeYaml()
    {
        return getGaeYamlModel().serialize();
    }

    /**
     * Loads the Yaml data from the given file.
     *
     * @param aAppYaml
     */
    protected String getYamlData(IFile aAppYaml)
    {
        try
        {
            return ZGaeResourceUtil.getContents(aAppYaml);
        }
        catch (ZGaeException e)
        {
            ZGaeUtil.logError(e);
            return "";
        }
    }

    /**
     * Writes YAML content to a .yaml file.
     *
     * @throws ZGaeException
     * @throws CoreException
     */
    private void writeYamlToFile(ZGaeYAMLModel aYamlModel, IFile aYamlFile) throws ZGaeException, CoreException
    {
        InputStream appYamlContent = getAsInputStream(aYamlModel);
        if (appYamlContent != null)
        {
            if (aYamlFile.exists())
            {
                aYamlFile.setContents(appYamlContent, true, true, null);
            }
            else
            {
                aYamlFile.create(appYamlContent, true, null);
            }
        }
    }

    /**
     * Gets the given yaml model as an input stream.
     *
     * @param aAppYamlModel
     * @throws ZGaeException
     */
    private InputStream getAsInputStream(ZGaeYAMLModel aAppYamlModel) throws ZGaeException
    {
        String yamlContent = aAppYamlModel.serialize();
        return ZGaeUTF8Util.getInputStream(yamlContent);
    }

    /**
     * @return the applicationRoot
     */
    public IContainer getApplicationRoot()
    {
        return mApplicationRoot;
    }

    /**
     * @param aApplicationRoot the applicationRoot to set
     */
    protected void setApplicationRoot(IContainer aApplicationRoot)
    {
        mApplicationRoot = aApplicationRoot;
    }

    /**
     * @return the appYamlModel
     */
    protected ZGaeYAMLModel getAppYamlModel()
    {
        if (mAppYamlModel == null)
            mAppYamlModel = new ZGaeYAMLModel();
        return mAppYamlModel;
    }

    /**
     * @param aAppYamlModel the appYamlModel to set
     */
    protected void setAppYamlModel(ZGaeYAMLModel aAppYamlModel)
    {
        mAppYamlModel = aAppYamlModel;
    }

    /**
     * @return the gaeYamlModel
     */
    protected ZGaeYAMLModel getGaeYamlModel()
    {
        if (mGaeYamlModel == null)
            mGaeYamlModel = new ZGaeYAMLModel();
        return mGaeYamlModel;
    }

    /**
     * @param aGaeYamlModel the gaeYamlModel to set
     */
    protected void setGaeYamlModel(ZGaeYAMLModel aGaeYamlModel)
    {
        mGaeYamlModel = aGaeYamlModel;
    }
}
